 /*

  xmunipack - batch astrometry calibration

  Copyright © 2012 F.Hroch (hroch@physics.muni.cz)

  This file is part of Munipack.

  Munipack is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
  
  Munipack is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with Munipack.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "xmunipack.h"
#include <wx/wx.h>
#include <wx/animate.h>
#include <wx/statline.h>
#include <wx/dataview.h>
#include <wx/richtooltip.h>
#include <wx/regex.h>
#include <fitsio.h>
#include <string.h>

#define BUTTLABEL "Process all Images"

#define ARCSEC "arcsec"
#define ARCMIN "arcmin"
#define ARCDEG "deg"

using namespace std;



MuniAstrometer::MuniAstrometer(wxWindow *w, MuniConfig *c, const std::vector<FitsMeta>& l): 
  wxDialog(w,wxID_ANY,"Astrometry",wxDefaultPosition,wxDefaultSize,wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER),
  config(c),
  list(l),fpick(0),catbutt(0),choice_alpha(0),choice_delta(0),
  timer(this),pipe(this),reftype(ID_ASTRO_REF),findex(-1),lastrow(0),
  nmatch(config->astrometry_nmatch),maxmatch(config->astrometry_maxmatch),
  output_units(config->astrometry_units), running(false),enable_backup(true)
{
  SetIcon(config->munipack_icon);

  CreateControls();

  SetTable();
}

MuniAstrometer::~MuniAstrometer()
{
  config->astrometry_units = output_units;
  config->astrometry_nmatch = nmatch;
  config->astrometry_maxmatch = maxmatch;

  if( !vofile.IsEmpty() )
    wxRemoveFile(vofile);
}

void MuniAstrometer::CreateControls()
{
  wxSizerFlags lf, cf, uf, sl, rl;
  lf.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT);
  cf.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT);
  uf.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT);
  sl.Border().Expand();
  rl.Align(wxALIGN_CENTER_VERTICAL).Border(wxRIGHT);

  wxStaticText *label;

  wxFont bf(*wxNORMAL_FONT);
  bf.SetWeight(wxFONTWEIGHT_BOLD);

  wxFont sf(*wxSMALL_FONT);

  // title
  wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);


  // wxBoxSizer *st = new wxBoxSizer(wxHORIZONTAL);

  // MuniArtIcons ico1(wxART_MESSAGE_BOX,wxSize(48,48));
  // st->Add(new wxStaticBitmap(this,wxID_ANY,ico1.Icon(wxART_EXECUTABLE_FILE)),wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
  // wxStaticText *title = new wxStaticText(this,wxID_ANY,"Astrometry");
  // title->SetFont(bf);
  // st->Add(title,wxSizerFlags().DoubleBorder().Center());
  // topsizer->Add(st,wxSizerFlags().Border().Center());

  // topsizer->Add(new wxStaticLine(this,wxID_ANY),sl);

  wxBoxSizer *ts = new wxBoxSizer(wxHORIZONTAL);

  label = new wxStaticText(this,wxID_ANY,"Reference:");
  label->SetFont(sf);
  topsizer->Add(label,wxSizerFlags().DoubleBorder(wxRIGHT|wxLEFT|wxTOP));

  wxRadioButton *rref, *rcat, *rvo;
  rref = new wxRadioButton(this,ID_ASTRO_REF,"Image",wxDefaultPosition,wxDefaultSize,wxRB_GROUP);
  rcat = new wxRadioButton(this,ID_ASTRO_CAT,"Table");
  rvo = new wxRadioButton(this,ID_ASTRO_VO,"Catalogues");
  ts->Add(rref,rl);
  ts->Add(rcat,rl);
  ts->Add(rvo,rl);
  topsizer->Add(ts,wxSizerFlags().Center().Border());

  refsizer = new wxBoxSizer(wxVERTICAL);
  RefLayout();
  topsizer->Add(refsizer,wxSizerFlags().Center());

  topsizer->Add(new wxStaticLine(this,wxID_ANY),sl);
  label = new wxStaticText(this,wxID_ANY,"Parameters:");
  label->SetFont(sf);
  topsizer->Add(label,wxSizerFlags().DoubleBorder(wxLEFT));

  wxFlexGridSizer *grid = new wxFlexGridSizer(2);
  grid->AddGrowableCol(1);

  label = new wxStaticText(this,wxID_ANY,"Projection:");
  grid->Add(label,lf);
  wxArrayString projections;
  projections.Add("Gnomonic");
  wxChoice *proj = new wxChoice(this,wxID_ANY,wxDefaultPosition,wxDefaultSize,projections);
  proj->SetSelection(0);
  grid->Add(proj,cf);
  topsizer->Add(grid,wxSizerFlags().Center());

  topsizer->Add(new wxStaticLine(this,wxID_ANY),wxSizerFlags().Expand().Border(wxLEFT|wxRIGHT|wxTOP));
  /*
  label = new wxStaticText(this,wxID_ANY,"Processing:");
  label->SetFont(sf);
  topsizer->Add(label,wxSizerFlags().DoubleBorder(wxLEFT));
  */

  mtable = new wxDataViewListCtrl(this,wxID_ANY);
  mtable->AppendTextColumn("Files");
  mtable->AppendTextColumn("Status",wxDATAVIEW_CELL_ACTIVATABLE,100);
  topsizer->Add(mtable,wxSizerFlags(1).Border().Expand());

  gstat = new wxGauge(this,wxID_ANY,static_cast<int>(list.size()),wxDefaultPosition,
		      wxDefaultSize,wxGA_HORIZONTAL|wxGA_SMOOTH);
  topsizer->Add(gstat,wxSizerFlags().Expand().Border(wxRIGHT|wxLEFT|wxBOTTOM));

  wxCollapsiblePane *collPane = new wxCollapsiblePane(this,wxID_ANY,"Options");
  wxWindow *win = collPane->GetPane();

  wxFlexGridSizer *gsizer = new wxFlexGridSizer(2);
  gsizer->AddGrowableCol(1);

  gsizer->Add(new wxStaticText(win,wxID_ANY,"Create Backups:"),
	      wxSizerFlags().Border().Align(wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL));
  wxCheckBox *check_backup = new wxCheckBox(win,wxID_ANY,"Enable");
  check_backup->SetToolTip("Backup are created. Switch-off may lead to any data LOST!");
  gsizer->Add(check_backup,wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT));
  check_backup->SetValue(true);

  gsizer->Add(new wxStaticText(win,wxID_ANY,"Output Units:"),
	      wxSizerFlags().Border().Align(wxALIGN_RIGHT));
  wxArrayString xunits;
  xunits.Add(ARCSEC);
  xunits.Add(ARCMIN);
  xunits.Add(ARCDEG);
  wxChoice *ounits = new wxChoice(win,wxID_ANY,wxDefaultPosition,wxDefaultSize,xunits);
  ounits->SetToolTip("Units for list of residuals and scale in FITS header list.");
  gsizer->Add(ounits,wxSizerFlags().Left());
  ounits->SetSelection(ounits->FindString(output_units));

  gsizer->Add(new wxStaticText(win,wxID_ANY,"Match Sequence:"),
	      wxSizerFlags().Border().Align(wxALIGN_RIGHT));
  wxSpinCtrl *snmatch = new wxSpinCtrl(win,wxID_ANY,wxEmptyString,wxDefaultPosition,wxDefaultSize,
				       wxSP_ARROW_KEYS,3,100,nmatch);
  gsizer->Add(snmatch,wxSizerFlags().Left());

  gsizer->Add(new wxStaticText(win,wxID_ANY,"Full Sequence:"),
	      wxSizerFlags().Border().Align(wxALIGN_RIGHT));
  wxSpinCtrl *smmatch = new wxSpinCtrl(win,wxID_ANY,wxEmptyString,wxDefaultPosition,wxDefaultSize,
				       wxSP_ARROW_KEYS,5,666,maxmatch);
  gsizer->Add(smmatch,wxSizerFlags().Left());
  
  snmatch->SetToolTip("Count of objects in the match sequence. Set under 5 leads to an uncertain matching, over 20 is very slow.");
  smmatch->SetToolTip("Maximum of objects in the match sequence. Crowded fields requires high values.");

  win->SetSizer(gsizer);
  gsizer->SetSizeHints(win);
  topsizer->Add(collPane,wxSizerFlags().Expand());

  butt = new wxButton(this,wxID_ANY,BUTTLABEL);
  topsizer->Add(butt,wxSizerFlags().Border().Expand());

  SetSizerAndFit(topsizer);


  Bind(wxEVT_CLOSE_WINDOW,&MuniAstrometer::OnClose,this);
  Bind(wxEVT_COMMAND_RADIOBUTTON_SELECTED,&MuniAstrometer::OnRefType,this,ID_ASTRO_REF);
  Bind(wxEVT_COMMAND_RADIOBUTTON_SELECTED,&MuniAstrometer::OnRefType,this,ID_ASTRO_CAT);
  Bind(wxEVT_COMMAND_RADIOBUTTON_SELECTED,&MuniAstrometer::OnRefType,this,ID_ASTRO_VO);
  Bind(wxEVT_COMMAND_BUTTON_CLICKED,&MuniAstrometer::OnProcess,this,butt->GetId());
  Bind(wxEVT_COMMAND_CHECKBOX_CLICKED,&MuniAstrometer::OnCheckBackup,this,check_backup->GetId());
  Bind(wxEVT_COMMAND_CHOICE_SELECTED,&MuniAstrometer::OnChoiceUnits,this,ounits->GetId());
  Bind(wxEVT_COMMAND_SPINCTRL_UPDATED,&MuniAstrometer::OnSpinNMatch,this,snmatch->GetId());
  Bind(wxEVT_COMMAND_SPINCTRL_UPDATED,&MuniAstrometer::OnSpinMMatch,this,smmatch->GetId());

}

void MuniAstrometer::OnClose(wxCloseEvent& event)
{
  wxLogDebug("MuniAstrometer::OnClose %s",running ? "T" : "F");

  // Arranging of the code is really important
  // A use of Close() doesn't work because the event-system have no break
  // to execute other events (finishing subprocesses).
  if( running ) {
    pipe.Stop();
    wxQueueEvent(this,event.Clone());
  }
  else
    // Skipping here passes processing of Close event to default handler.
    event.Skip();
}

void MuniAstrometer::SetTable()
{
  for(size_t i = 0; i < list.size(); i++) {
    wxVector<wxVariant> line;
    line.push_back(list[i].GetName());
    line.push_back("");
    mtable->AppendItem(line);
  }


}

void MuniAstrometer::OnRefType(wxCommandEvent& event)
{
  reftype = event.GetId();
  RefLayout();
  Layout();
}

void MuniAstrometer::RefLayout()
{
  wxSizerFlags lf, cf, uf, sl, rl;
  lf.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT);
  cf.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT);

  if( catbutt ) {
    Unbind(wxEVT_COMMAND_BUTTON_CLICKED,&MuniAstrometer::OnCatalogue,this,catbutt->GetId());
    catbutt->Destroy();
  }

  if( fpick ) {
    Unbind(wxEVT_COMMAND_FILEPICKER_CHANGED,&MuniAstrometer::OnRefFile,this,fpick->GetId());
    fpick->Destroy();
  }

  if( choice_alpha && choice_delta ) {
    Unbind(wxEVT_COMMAND_CHOICE_SELECTED,&MuniAstrometer::OnChoice,this,ID_ASTRO_CH_ALPHA);
    Unbind(wxEVT_COMMAND_CHOICE_SELECTED,&MuniAstrometer::OnChoice,this,ID_ASTRO_CH_DELTA);
    choice_alpha->Destroy();
    choice_delta->Destroy();
    key_alpha.Clear();
    key_delta.Clear();
  }
    
  refsizer->Clear(true);
  fpick = 0;
  catbutt = 0;
  choice_alpha = 0;
  choice_delta = 0;

  if( reftype == ID_ASTRO_REF || reftype == ID_ASTRO_CAT ) {

    wxBoxSizer *rfsizer = new wxBoxSizer(wxHORIZONTAL);
    wxStaticText *flabel = new wxStaticText(this,wxID_ANY,"File:");
    rfsizer->Add(flabel,lf);
    fpick = new wxFilePickerCtrl(this,wxID_ANY,"","Select A File",
				 "FITS files "+config->dirmask+")|"+
				 config->dirmask+"| All files (*)|*");
    rfsizer->Add(fpick,wxSizerFlags().Expand());
    refsizer->Add(rfsizer,wxSizerFlags().Border().Center());

    Bind(wxEVT_COMMAND_FILEPICKER_CHANGED,&MuniAstrometer::OnRefFile,this,fpick->GetId());
    
  }
  else if( reftype == ID_ASTRO_VO ) {

    catbutt = new wxButton(this,wxID_ANY,"Search Catalogues");
    refsizer->Add(catbutt,wxSizerFlags().Center().Border());
    Bind(wxEVT_COMMAND_BUTTON_CLICKED,&MuniAstrometer::OnCatalogue,this,catbutt->GetId());

  }

}

void MuniAstrometer::OnRefFile(wxFileDirPickerEvent& event)
{
  reffile = event.GetPath();

  if( reftype == ID_ASTRO_CAT ) {

    wxArrayString cols = FitsColumns(reffile);

    wxBoxSizer *s = new wxBoxSizer(wxHORIZONTAL);
    s->Add(new wxStaticText(this,wxID_ANY,"Key α:"),wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
    choice_alpha = new wxChoice(this,ID_ASTRO_CH_ALPHA,wxDefaultPosition,wxDefaultSize,cols);
    s->Add(choice_alpha,wxSizerFlags().DoubleBorder(wxRIGHT));
    s->Add(new wxStaticText(this,wxID_ANY,"Key δ:"),wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
    choice_delta = new wxChoice(this,ID_ASTRO_CH_DELTA,wxDefaultPosition,wxDefaultSize,cols);
    s->Add(choice_delta);
    refsizer->Add(s,wxSizerFlags().Center());
    Layout();
    Fit();  

    Bind(wxEVT_COMMAND_CHOICE_SELECTED,&MuniAstrometer::OnChoice,this,ID_ASTRO_CH_ALPHA);
    Bind(wxEVT_COMMAND_CHOICE_SELECTED,&MuniAstrometer::OnChoice,this,ID_ASTRO_CH_DELTA);

    int i1 = wxNOT_FOUND;
    int i2 = wxNOT_FOUND;

    for(size_t i = 0; i < cols.GetCount(); i++)
      if( cols[i].StartsWith("RA") ) {
	i1 = i;
	key_alpha = cols[i];
	break;
      }
    
    for(size_t i = 0; i < cols.GetCount(); i++)
      if( cols[i].StartsWith("DE") ) {
	i2 = i;
	key_delta = cols[i];
	break;
      }
    
    choice_alpha->SetSelection(i1);
    choice_delta->SetSelection(i2);
  }
}

void MuniAstrometer::OnCatalogue(wxCommandEvent& event)
{
  vector<VOResolv> vores;
  vores.push_back(VOResolv("UCAC 3","http://vizier.u-strasbg.fr/viz-bin/votable/-A?-source=I/315&",
			   "RAJ2000","DEJ2000","f.mag"));
  vores.push_back(VOResolv("GSC 2.3","http://vizier.u-strasbg.fr/viz-bin/votable/-A?-source=I/305&",
			   "RAJ2000","DEJ2000","Vmag"));
  
  MuniCone cone(this,config,vores);
  if( cone.ShowModal() == wxID_OK ) {
    vofile = wxFileName::CreateTempFileName("xmunipack-astrometer-search_");
    wxCopyFile(cone.GetPath(),vofile);
    key_alpha = cone.GetKeyAlpha();
    key_delta = cone.GetKeyDelta();
  }
}

void MuniAstrometer::OnChoice(wxCommandEvent& event)
{
  if( event.GetId() == ID_ASTRO_CH_ALPHA ) 
    key_alpha = event.GetString();
  if( event.GetId() == ID_ASTRO_CH_DELTA ) 
    key_delta = event.GetString();  
}

void MuniAstrometer::OnChoiceUnits(wxCommandEvent& event)
{
  if( event.GetString() == ARCDEG )
    output_units = "deg";
  else if( event.GetString() == ARCMIN )
    output_units = "arcmin";
  else
    output_units = "arcsec";
}

void MuniAstrometer::OnCheckBackup(wxCommandEvent& event)
{
  enable_backup = event.IsChecked();
}

void MuniAstrometer::OnSpinNMatch(wxSpinEvent& event)
{
  nmatch = event.GetPosition();
}

void MuniAstrometer::OnSpinMMatch(wxSpinEvent& event)
{
  maxmatch = event.GetPosition();
}

void MuniAstrometer::OnProcess(wxCommandEvent& event)
{
  // check input
  if( reffile.IsEmpty() && vofile.IsEmpty() ) {
    wxRichToolTip tip("Undefined Reference",
		      "Please define an astrometric\n"
                      "catalogue or an reference image.");
    tip.SetIcon(wxICON_WARNING);
    if( reffile.IsEmpty() && fpick != 0 )
      tip.ShowFor(fpick);
    if( vofile.IsEmpty() && catbutt != 0)
      tip.ShowFor(catbutt);
    return;
  }

  if( list.empty() ) {
    wxRichToolTip tip("Nothing to Calibrate",
		      "Please select some files\n"
                      "to astrometry calibration.");
    tip.SetIcon(wxICON_WARNING);
    tip.ShowFor(mtable);
    return;
  }


  if( running ) {

    pipe.Stop();
    mtable->SetTextValue("Interrupted.",findex,1);
  }

  else {

    Bind(wxEVT_END_PROCESS,&MuniAstrometer::OnFinish,this);
    Bind(wxEVT_TIMER,&MuniAstrometer::OnTimer,this);

    running = true;
    butt->SetLabel("STOP Processing");
    CreateProcess();
    timer.Start(250);
    pipe.Start();
    lastrow = 0;
  }
}


void MuniAstrometer::CreateProcess()
{
  MuniProcess *c = new MuniProcess(&pipe,"astrometry");
  pipe.push(c);

  c->Write("PROJECTION = 'GNOMONIC'");

  if( ! key_alpha.IsEmpty() && ! key_delta.IsEmpty() ) {
    c->Write("KEY_ALPHA = '"+key_alpha+"'");
    c->Write("KEY_DELTA = '"+key_delta+"'");
  }

  if( ! output_units.IsEmpty() )
    c->Write("AUNITS = '"+output_units+"'");

  c->Write("WCSSAVE = T");

  wxString a;
  a.Printf("NMATCH = %d",nmatch);
  c->Write(a);
  a.Printf("MAXMATCH = %d",maxmatch);
  c->Write(a);

  
  if( reftype == ID_ASTRO_REF )
    c->Write("REF = '" + reffile + "'");
    
  else if( reftype == ID_ASTRO_CAT )
    c->Write("CAT = '" + reffile + "'");

  else if( reftype == ID_ASTRO_VO )
    c->Write("CAT = '" + vofile + "'");


  if( enable_backup ) {

    const wxString suffix("~");
    for(size_t i = 0; i < list.size(); i++) {
      wxString f(list[i].GetFullPath());
      wxString b(f+suffix);
      wxFileName bf(b);
      if( bf.FileExists() ) {
	wxLogMessage("Backup file for `"+f+"' already exists or has no write permissions. Skipping.");
      }
      c->Write("FILE = '" + f + "' '" + b + "'");
    }
  }
  else {
    
    for(size_t i = 0; i < list.size(); i++) {
      wxString f(list[i].GetFullPath());
      wxFileName bf(f);
      if( bf.IsFileWritable() )
	c->Write("FILE = '" + f + "' '" + f + "'");
      else
	wxLogMessage("File `"+ f + "' has no write permissions. Skipping.");
    }    
  }

}

void MuniAstrometer::OnFinish(wxProcessEvent& event)
{
  wxLogDebug("MuniAstrometer::OnFinish");

  running = false;
  butt->SetLabel(BUTTLABEL);
  timer.Stop();

  Unbind(wxEVT_END_PROCESS,&MuniAstrometer::OnFinish,this);
  Unbind(wxEVT_TIMER,&MuniAstrometer::OnTimer,this);

  ParseOutput();
}

void MuniAstrometer::OnTimer(wxTimerEvent& event)
{
  ParseOutput();
}

void MuniAstrometer::ParseOutput()
{

  wxArrayString out(pipe.GetOutput());

  for(size_t j = lastrow; j < out.GetCount(); j++, lastrow++) {

    wxRegEx rc("^=> .+");
    wxASSERT(rc.IsValid());
    if( rc.Matches(out[j]) ) {
      wxString f(out[j].Mid(3));

      if( f.StartsWith("Astrometry calibration") ) {

	for(size_t i = 0; i < list.size(); i++)
	  if( f.Find(list[i].GetFullPath()) != wxNOT_FOUND ) {
	    findex = i;
	    gstat->SetValue(findex+1);
	    break;
	  }
      }
	

      wxRegEx r("^[A-Z][a-z]+ ...$");
      wxASSERT(r.IsValid());
      if( r.Matches(f) ) {
	if( findex >= 0 ) 
	  mtable->SetTextValue(r.GetMatch(f),findex,1);
      }

      wxRegEx rs("^[A-Z][a-z]+.$");
      wxASSERT(rs.IsValid());
      if( rs.Matches(f) ) {
	
	if( findex >= 0 ) {
	  mtable->SetTextValue(rs.GetMatch(f),findex,1);
	  findex = -1;
	}

      }
    }
  }
}

